import type { Context, Next, RequestOptions } from './context'
import { NOT_FOUND_CODE, NOT_FOUND_MESSAGE, VERIFY_CODE } from './constant'
import loadRouter from './load-router'
import { isAsyncFunction } from './utils'

export type Method = 'GET' | 'POST' | 'PUT' | 'DELETE'

export interface IHookArg {
  (ctx: Context, next: Next): Promise<any> | any
  _async?: boolean
}
export interface IErrHookArg {
  (err: any, ctx: Context, next: Next): Promise<any> | any
  _async?: boolean
}
export interface RouteBody {
  method?: Method
  path: string
  fullUrl?: string
  disabledVerify?: boolean
  schema?: any
  onRequest?: IHookArg
  preHandler?: IHookArg
  onResponse?: IHookArg
  onTimeout?: IHookArg
  onError?: (err: Error, ctx: Context, next: Next) => Promise<any> | any
  handler: (ctx: Context, req: RequestOptions) => Promise<any> | any
}

export default class Router {
  public routes: Map<string, RouteBody> = new Map()
  docJsonList: any[] = []
  private routeRegexCache?: Map<string, { regex: RegExp, paramNames: string[] }> = new Map()

  constructor() {
  }

  route(options: RouteBody) {
    const { method, path } = options
    const key = `${method}:${path}`
    if (!this.routes.get(key)) {
      this.routes.set(key, options)
    }
  }

  // 现在只支持get和post，其他的同理
  get(path: string, handler: any) {
    this.route({ method: 'GET', path, handler })
  }

  post(path: string, handler: any) {
    this.route({ method: 'POST', path, handler })
  }

  findHandler(ctx: Context) {
    const path = ctx.request.path as string
    const method = ctx.request.method as Method
    const route = this.routes.get(`${method}:${path}`)

    if (route) return route

    if (!this.routeRegexCache) {
      this.routeRegexCache = new Map()
    }

    for (const [key, handler] of this.routes) {
      const [routeMethod, pattern] = key.split(':')
      if (routeMethod !== method) continue

      // 使用缓存的正则表达式，如果没有则创建并缓存
      let regexEntry = this.routeRegexCache.get(pattern)
      if (!regexEntry) {
        const paramNames: string[] = []
        const regexPattern = pattern.replace(/\[([^/]+)\]/g, (_, paramName) => {
          paramNames.push(paramName)
          return '([^/]+)'
        })
        regexEntry = {
          regex: new RegExp(`^${regexPattern}$`),
          paramNames,
        }
        this.routeRegexCache.set(pattern, regexEntry)
      }

      const { regex, paramNames } = regexEntry
      const match = path.match(regex)

      if (match) {
        // 提取路径参数并添加到上下文
        const params: Record<string, string> = {}
        paramNames.forEach((name, index) => {
          params[name] = match[index + 1]
        })

        ctx.params = ctx.request.params = params
        return handler
      }
    }
    return null
  }

  autoRouter({ controllerDir, test, prefix = '', createDoc = true, defaultMethod = 'GET' } = {} as { controllerDir: string, prefix?: string, defaultMethod: Method, test?: (fileName: string, route: string) => string | undefined, createDoc?: boolean }) {
    const controllers = loadRouter({ controllerDir, test })()
    for (const route of Object.keys(controllers)) {
      const router = controllers[route]
      const service = router.handler

      this.docJsonList.push({ disabled: service.disabled, path: router.path, method: service.method || 'GET', schema: service.schema })

      if (service.disabled) continue
      service.onRequest && (service.onRequest._async = isAsyncFunction(service.onRequest))
      service.onResponse && (service.onResponse._async = isAsyncFunction(service.onResponse))
      service.onTimeout && (service.onTimeout._async = isAsyncFunction(service.onTimeout))
      service.onError && (service.onError._async = isAsyncFunction(service.onError));

      (service.method || defaultMethod).split('|').forEach((method: Method) => {
        this.route({
          path: (prefix && `/${prefix}`) + router.path,
          fullUrl: router.fullUrl,
          method,
          schema: service.schema,
          onRequest: service.onRequest,
          onResponse: service.onResponse,
          onTimeout: service.onTimeout,
          onError: service.onError,
          handler: service.handler,
          disabledVerify: service.disabledVerify,
        })
      })
    }

    if (createDoc) {
      this.route({
        path: `/__doc__`,
        method: 'GET',
        handler: (ctx) => {
          ctx.send({ message: '文档获取成功', code: 200, data: this.docJsonList.filter(e => e.path !== '/__doc__') })
        },
      })
    }
    return controllers
  }

  routerMiddleware() {
    return async (ctx: Context, next: any) => {
      const route = this.findHandler(ctx)

      if (!route) {
        ctx.throw(NOT_FOUND_CODE, NOT_FOUND_MESSAGE)
        return
      }

      // 执行路由级 onRequest 钩子
      route.onRequest && await ctx.app.runHook('onRequest', route.onRequest, [ctx])

      // Schema 验证
      if (route.schema && !route.disabledVerify) {
        if (ctx.request.query && route.schema.query) {
          const validationResult = ctx.verify(ctx.request.query, route.schema.query)
          if (!validationResult.valid) {
            ctx.throw(VERIFY_CODE, validationResult.errors.join(';'))
          }
        }

        if (ctx.request.params && route.schema.params) {
          const validationResult = ctx.verify(ctx.request.params, route.schema.params)
          if (!validationResult.valid) {
            ctx.throw(VERIFY_CODE, validationResult.errors.join(';'))
          }
        }
        if (ctx.request.body && route.schema.body) {
          const validationResult = ctx.verify(ctx.request.body, route.schema.body)
          if (!validationResult.valid) {
            ctx.throw(VERIFY_CODE, validationResult.errors.join(';'))
          }
        }
      }
      // 执行处理函数
      const res = await route.handler.call(ctx, ctx, ctx.request)
      if (ctx.response.body === null) {
        ctx.send(res)
      }
      // 执行路由级 onResponse 钩子
      if (route.onResponse) {
        return ctx.app.runHook('onResponse', route.onResponse, [ctx])
      }
    }
  }
}
